﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;
using System.Security.Principal;
using ActiveDirectoryUtilities.DirectoryServiceAccess;

namespace ActiveDirectoryUtilities.CheckDsAcls
{
	public class ActiveDirectoryAcls : IEnumerable, IEnumerator
	{
		static private Dictionary<string, ActiveDirectorySecurity> _defaultObjectClassSecurityDescriptor = new Dictionary<string, ActiveDirectorySecurity>();

		private bool _showChildren
			, _showSacls
			, _showDacls;
		private IdentityReference _targetAccount;
		private string _targetDn = string.Empty;
		private string _targetServer = string.Empty;
		private int _port = (int)LDAP_PORTS.Global_Catalog;
		private string _ldapSearchFilter = "(objectClass=*)";

		private WLdapSearchResultCollection _src = null;

		#region Properties
		/// <summary>
		/// Show the ACLs on the specified object and entire heirarchy below the object
		/// </summary>
		public bool ShowChildren
		{
			get { return _showChildren; }
			set { _showChildren = value; }
		}
		/// <summary>
		/// Return the SACLs on the object(s)
		/// </summary>
		public bool ShowSacls
		{
			get { return _showSacls; }
			set { _showSacls = value; }
		}
		/// <summary>
		/// Do not show the DACLs on the object(s)
		/// <para>If this.ShowSacls is not true, then no data will be displayed.</para>
		/// </summary>
		public bool ShowDacls
		{
			get { return _showDacls; }
			set { _showDacls = value; }
		}
		/// <summary>
		/// Only return the ACEs that contain this security identity (to find where this identity is permissioned)
		/// </summary>
		public IdentityReference AccountToSearchFor
		{
			get { return _targetAccount; }
			set { _targetAccount = value; }
		}
		/// <summary>
		/// The root of the search
		/// </summary>
		public string TargetDistinguishedName
		{
			get { return _targetDn; }
			set { _targetDn = value; }
		}
		/// <summary>
		/// LDAP search filter format to narrow the objects looked at.
		/// </summary>
		public string LdapSearchFilter
		{
			get { return _ldapSearchFilter; }
			set { _ldapSearchFilter = value; }
		}
		#endregion

		public ActiveDirectoryAcls(string targetDistinguishedName, string targetServer, int port)
		{
			_targetDn = targetDistinguishedName;
			_targetServer = targetServer;
			_port = port;
		}

		private bool isQueryStarted()
		{
			if (_src == null)
			{
				WLdapSearchRequest wlsr = new WLdapSearchRequest(_targetServer, _port, _targetDn, _ldapSearchFilter);
				wlsr.Scope = _showChildren ? System.DirectoryServices.Protocols.SearchScope.Subtree : System.DirectoryServices.Protocols.SearchScope.Base;
				wlsr.Attributes.Add("objectClass");
				wlsr.Attributes.Add("ntSecurityDescriptor");

				#region configure which portions of the security descriptor to return
				SecurityDescriptorFlagControl sdfc = new SecurityDescriptorFlagControl();
				sdfc.SecurityMasks = System.DirectoryServices.Protocols.SecurityMasks.Owner | System.DirectoryServices.Protocols.SecurityMasks.Group;
				if (_showDacls)
				{
					sdfc.SecurityMasks = sdfc.SecurityMasks | System.DirectoryServices.Protocols.SecurityMasks.Dacl;
				}

				if (_showSacls)
				{
					sdfc.SecurityMasks = sdfc.SecurityMasks | System.DirectoryServices.Protocols.SecurityMasks.Sacl;
				}
				wlsr.Controls.Add(sdfc);
				#endregion

				if (_targetDn.ToLower().Contains("cn=deleted objects,"))
				{
					ShowDeletedControl sdc = new ShowDeletedControl();
					wlsr.Controls.Add(sdc);
				}

				try
				{
					_src = wlsr.FindAll();
				}
				catch (LdapException ex)
				{
					if (ex.ErrorCode == 81)
					{
						throw new ApplicationException("Could not find a directory server for " + _targetServer);
					}
					throw ex;
				}
				catch (DirectoryOperationException ex)
				{
					throw new ApplicationException(ex.Message + ":  " + _targetDn + "\n\rThe closest match:  " + ex.Response.MatchedDN);
				}
			}
			return true;
		}

		#region IEnumerable Members

		public IEnumerator GetEnumerator()
		{
			return this;
		}

		#endregion

		#region IEnumerator Members

		public object Current
		{
			get
			{
				string objectClass = string.Empty;
				if (_src.Current.Attributes.Contains("objectClass"))
				{
					objectClass = _src.Current.Attributes["objectClass"][_src.Current.Attributes["objectClass"].Count - 1].ToString();
				}

				byte[] ntSecurityDescriptor = null;
				if (_src.Current.Attributes.Contains("ntSecurityDescriptor"))
				{
					ntSecurityDescriptor = (byte[])_src.Current.Attributes["ntSecurityDescriptor"][0];
				}
				return new ActiveDirectoryAcl(_src.Current.DistinguishedName, objectClass, ntSecurityDescriptor, _showDacls, _showSacls);
			}
		}

		public bool MoveNext()
		{
			//Check to see if AD has been queried
			if (this.isQueryStarted())
			{
				//Check to see if there is another object in the LDAP search result set.
				return _src.MoveNext();
			}
			//If we make it here we should have run out of objects.
			return false;
		}

		public void Reset()
		{
			_src.Dispose();
			_src = null;
		}

		#endregion
	}
}
